#include "a4.hpp"
#include "image.hpp"

void a4_render(// What to render
               SceneNode* root,
               // Where to output the image
               const std::string& filename,
               // Image size
               int width, int height,
               // Viewing parameters
               const Point3D& eye, const Vector3D& view,
               const Vector3D& up, double fov,
               // Lighting parameters
               const Colour& ambient,
               const std::list<Light*>& lights
               )
{
  // Fill in raytracing code here.
//  std::cerr << "Stub: a4_render(" << root << ",\n     "
//            << filename << ", " << width << ", " << height << ",\n     "
//            << eye << ", " << view << ", " << up << ", " << fov << ",\n     "
//            << ambient << ",\n     {";

//  for (std::list<Light*>::const_iterator I = lights.begin(); I != lights.end(); ++I) {
//    if (I != lights.begin()) std::cerr << ", ";
//   	std::cerr << **I;
//  }
//  std::cerr << "});" << std::endl;
  
  // For now, just make a sample image.

  Image img(width, height, 3);
	bool hit;
	int counter = 0;
	for (int y = 0; y < height; y++) {
		for (int x = 0; x < width; x++) {
			Vector3D V = get_ray_direction(x,y,width,height,eye,view,up,fov);
			V.normalize();
			Ray m_ray(eye, V);
			//Intersection m_intersection;
			Colour illumination(0,0,0);
			hit = raytrace(m_ray, &illumination, ambient, root, lights, 0);
			if(!hit){
				//background
				illumination = Colour(0,0, (double)y/height);
			}
			img(x,y,0)= illumination.R();
			img(x,y,1)= illumination.G();
			img(x,y,2)= illumination.B();
    		}
		int complete = (int)((double)(y+1)/height*100.0);
		if(complete == counter){ 
			std::cout<< complete << "% COMPLETED" << std::endl;
			counter += 10;	
		}  	
	}
  img.savePng(filename);
  
}

bool raytrace(Ray ray, Colour* illumination, const Colour& ambient,
 			SceneNode* root, const std::list<Light*>& lights,
			int depth)
{
	if(depth > 2) return false;
	
	Intersection m_intersection;
	bool hit = root->ray_intersect(ray, &m_intersection);
	if(hit){
		do_phong_reflection(m_intersection, ray, root, lights, illumination, ambient);
		//reflection
		Material *material = m_intersection.m_material;
		double mir_coef = material->get_reflection();
		if(mir_coef > 0.0){
			Vector3D N = m_intersection.m_normal;
			Vector3D V = ray.get_direction();
			Vector3D temp = V;
			N.normalize();
			V.normalize();
			Vector3D R(V - 2.0 * V.dot(N) * N);
			R.normalize();
			Ray reflect_ray(m_intersection.m_intersect, R);
			Colour reflect_illum(0,0,0);
			if(raytrace(reflect_ray, &reflect_illum, ambient, root, lights, depth+1)){
				Colour diff = m_intersection.m_material->get_diffuse();
				*illumination = *illumination + reflect_illum*diff*mir_coef;
			}
		}
	}
	*illumination = Colour(std::min(1.0, illumination->R()),
				std::min(1.0, illumination->G()),
				std::min(1.0, illumination->B()));
	return hit;
}

/* Illumination = ambient + SUM( diffuse*(light_ray.normal)*lightintensity + 
 *							specular(reflect.view_ray)^shininess*lightintensity )
 * reflect = 2(light_ray.normal)*normal - light_ray
 * all vectors are normalized
 */
void do_phong_reflection(const Intersection& intersection, Ray ray, SceneNode* root,
 				const std::list<Light*>& lights, Colour* illumination, const Colour& ambient)
{
	Material *material = intersection.m_material;
	Colour m_diffuse = material->get_diffuse();
	Colour m_specular = material->get_specular();
	double m_shininess = material->get_shininess();
	Point3D hit_pos = intersection.m_intersect;
	Vector3D hit_normal = intersection.m_normal;
	Vector3D view_ray = - ray.get_direction();
	hit_normal.normalize();
	view_ray.normalize();
	*illumination = *illumination + ambient*m_diffuse;
	for (std::list<Light*>::const_iterator I = lights.begin(); I != lights.end(); ++I) {
		Point3D light_pos = (*I)->position;
		Vector3D light_ray = light_pos - hit_pos;
		light_ray.normalize();
		Intersection shadow_intersection;
		Ray light(hit_pos, light_ray);
		bool shadow = root->ray_intersect(light, &shadow_intersection);
		if(!shadow){
			Vector3D reflect = 2.0f * (light_ray.dot(hit_normal)) * hit_normal - light_ray;
			reflect.normalize();
			if(light_ray.dot(hit_normal) > 0)
				*illumination = *illumination + m_diffuse*(light_ray.dot(hit_normal))*(*I)->colour;
#ifdef BLIN
			Vector3D H = (view_ray+light_ray);
			H.normalize();
			if(H.dot(hit_normal) > 0)
				*illumination = *illumination + m_specular*pow(H.dot(hit_normal), m_shininess)*(*I)->colour;
#else
			if(reflect.dot(view_ray) > 0)
				*illumination = *illumination + m_specular*pow(reflect.dot(view_ray), m_shininess)*(*I)->colour;
#endif 
		}
	}
	
}

Vector3D get_ray_direction(int x,int y,int width,int height,
	Point3D eye, Vector3D view, Vector3D up, double fov)
{
	Point3D center = eye + view;
	// distance to the screen
	double d = sqrt(view.dot(view));
	// tangent
	double factor = tan((fov*M_PI)/360);
	double h = factor * d * 2 ;
	double w = width/height * h;
	x = x - width/2;
	y = y - height/2;
	Vector3D right = view.cross(up);
  	up.normalize();
  	right.normalize();
	center = center + x*(w/width)*right - y*(h/height)*up;
	return center - eye;
}
